﻿#ifdef AS_STACK_WALKER


#include "as_crash.h"
#include <time.h>
#include <string>
auto FormatOutputString(const char *Prefix, const char *Description, bool useTimeStamp) -> std::string
{
	char p[MAX_PATH];
	p[0] = 0;
	time_t t = time(NULL);
	tm *a = gmtime(&t);
	strcat(p, Prefix);
	strcat(p, "/");
	strcat(p, Description);
	if (useTimeStamp)
	{
		char ftime[100];
		snprintf(ftime, 100, "-%-4d-%02d-%02d %02d-%02d-%02d", a->tm_year + 1900, a->tm_mon + 1, a->tm_mday, a->tm_hour, a->tm_min, a->tm_sec);
		ftime[99] = '\0';
		strcat(p, ftime);
	}

	strcat(p, ".log");
	return std::string(p);
};

void OutputCrashLogLine(const char *format, ...)
{
	std::string s = FormatOutputString("logs", "CrashLog", false);
	FILE *m_file = fopen(s.c_str(), "a");
	if (!m_file) return;

	va_list ap;
	va_start(ap, format);
	vfprintf(m_file, format, ap);
	fprintf(m_file, "\n");
	fclose(m_file);
	va_end(ap);
}

#ifdef _WIN32
#include <stdio.h>
#include <tchar.h>
#include <mutex>
#  pragma warning( disable : 4311 )
bool ON_CRASH_BREAK_DEBUGGER = false;
_invalid_parameter_handler preIph = NULL;
_purecall_handler prePch = NULL;

void NewInvalidParameterHandler(const wchar_t *expression, const wchar_t *function, const wchar_t *file, unsigned int line, uintptr_t pReserved)
{
	//simple_log(L"MyPureCallHandler");
	//The parameters all have the value NULL unless a debug version of the CRT library is used.
	//PrintCrashInformation(nullptr);
	HandleCrash(NULL);
}

void NewPureCallHandler(void)
{
	//PrintCrashInformation(nullptr);
	HandleCrash(NULL);
}

void StartCrashHandler()
{
	ON_CRASH_BREAK_DEBUGGER = (IsDebuggerPresent() == TRUE) ? true : false;
	preIph = _set_invalid_parameter_handler(NewInvalidParameterHandler);
	prePch = _set_purecall_handler(NewPureCallHandler);
}

///////////////////////////////////////////////////////////////////////////////
// GetExceptionDescription
// Translate the exception code into something human readable
static const char *GetExceptionDescription(DWORD ExceptionCode)
{
	struct ExceptionNames
	{
		DWORD	ExceptionCode;
		const char *ExceptionName;
	};

	ExceptionNames ExceptionMap[] =
	{
		{0x40010005, ("a Control-C")},
		{0x40010008, ("a Control-Break")},
		{0x80000002, ("a Datatype Misalignment")},
		{0x80000003, ("a Breakpoint")},
		{0xc0000005, ("an Access Violation")},
		{0xc0000006, ("an In Page Error")},
		{0xc0000017, ("a No Memory")},
		{0xc000001d, ("an Illegal Instruction")},
		{0xc0000025, ("a Noncontinuable Exception")},
		{0xc0000026, ("an Invalid Disposition")},
		{0xc000008c, ("a Array Bounds Exceeded")},
		{0xc000008d, ("a Float Denormal Operand")},
		{0xc000008e, ("a Float Divide by Zero")},
		{0xc000008f, ("a Float Inexact Result")},
		{0xc0000090, ("a Float Invalid Operation")},
		{0xc0000091, ("a Float Overflow")},
		{0xc0000092, ("a Float Stack Check")},
		{0xc0000093, ("a Float Underflow")},
		{0xc0000094, ("an Integer Divide by Zero")},
		{0xc0000095, ("an Integer Overflow")},
		{0xc0000096, ("a Privileged Instruction")},
		{0xc00000fD, ("a Stack Overflow")},
		{0xc0000142, ("a DLL Initialization Failed")},
		{0xe06d7363, ("a Microsoft C++ Exception")},
	};

	for (int i = 0; i < sizeof(ExceptionMap) / sizeof(ExceptionMap[0]); i++)
		if (ExceptionCode == ExceptionMap[i].ExceptionCode)
			return ExceptionMap[i].ExceptionName;

	return _T("an Unknown exception type");
}

void echo(const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	vprintf(format, ap);
	std::string s = FormatOutputString("logs", "CrashLog", false);
	FILE *m_file = fopen(s.c_str(), "a");
	if (!m_file)
	{
		va_end(ap);
		return;
	}

	vfprintf(m_file, format, ap);
	fclose(m_file);
	va_end(ap);
}

void PrintCrashInformation(PEXCEPTION_POINTERS except)
{
	// 基于当前系统的当前日期/时间
	time_t now = time(0);
	// 把 now 转换为字符串形式
	char *dt = ctime(&now);
	echo("-=-=-=-=-=-=-=-=-=-=-=-=-Time:%s=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n", dt);
	if (except)
	{
		echo("Server has crashed. Reason was:\n");
		echo("   %s at 0x%08X\n", GetExceptionDescription(except->ExceptionRecord->ExceptionCode),
			except->ExceptionRecord->ExceptionAddress);
	}
#ifdef REPACK
	echo("%s repack by %s has crashed. Visit %s for support.", REPACK, REPACK_AUTHOR, REPACK_WEBSITE);
#endif
	echo("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
	echo("Call Stack: \n");
	as::tools::AsStackWalker sw;
	sw.ShowCallstack();
	echo("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
}

bool died = false;
std::mutex m_crashLock;

int __cdecl HandleCrash(PEXCEPTION_POINTERS pExceptPtrs)
{
	if (pExceptPtrs == 0)
	{
		// Raise an exception :P
		__try
		{
			RaiseException(EXCEPTION_BREAKPOINT, 0, 0, 0);
		}
		__except (HandleCrash(GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION)
		{
		}
	}

	/* only allow one thread to crash. */
	bool islock = true;
	if (!m_crashLock.try_lock())
	{
		islock = false;
		TerminateThread(GetCurrentThread(), -1);
		// not reached
	}

	if (died)
	{
		TerminateProcess(GetCurrentProcess(), -1);
		// not reached:P
	}

	died = true;

	// Create the date/time string
	time_t curtime = time(NULL);
	tm *pTime = localtime(&curtime);
	char filename[MAX_PATH];
	TCHAR modname[MAX_PATH * 2];
	ZeroMemory(modname, sizeof(modname));
	if (GetModuleFileName(0, modname, MAX_PATH * 2 - 2) <= 0)
		strcpy(modname, "UNKNOWN");

	char *mname = strrchr(modname, '\\');
	(void *)mname++;	 // Remove the last

	sprintf(filename, "CrashDumps\\dump-%s-%u-%u-%u-%u-%u-%u-%u.dmp",
		mname, pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday,
		pTime->tm_hour, pTime->tm_min, pTime->tm_sec, GetCurrentThreadId());

	HANDLE hDump = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);

	if (hDump == INVALID_HANDLE_VALUE)
	{
		// Create the directory first
		CreateDirectory("CrashDumps", 0);
		hDump = CreateFile(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
			FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
	}

	PrintCrashInformation(pExceptPtrs);
	// beep
	//printf("\x7");
	printf("\nCreating crash dump file %s\n", filename);

	if (hDump == INVALID_HANDLE_VALUE)
	{
		MessageBox(0, "Could not open crash dump file.", "Crash dump error.", MB_OK);
	}
	else
	{
		MINIDUMP_EXCEPTION_INFORMATION info;
		info.ClientPointers = FALSE;
		info.ExceptionPointers = pExceptPtrs;
		info.ThreadId = GetCurrentThreadId();

		MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
			hDump, MiniDumpWithIndirectlyReferencedMemory, &info, 0, 0);

		CloseHandle(hDump);
	}
	SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
	OnCrash(!ON_CRASH_BREAK_DEBUGGER);
	if (islock)
		m_crashLock.unlock();
	return EXCEPTION_CONTINUE_SEARCH;
}

void as::tools::AsStackWalker::OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName)
{
}

void as::tools::AsStackWalker::OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion)
{
}

void as::tools::AsStackWalker::OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry)
{
	CHAR buffer[STACKWALK_MAX_NAMELEN];
	if ((eType != lastEntry) && (entry.offset != 0))
	{
		if (entry.name[0] == 0)
			strcpy(entry.name, "(function-name not available)");
		if (entry.undName[0] != 0)
			strcpy(entry.name, entry.undName);
		if (entry.undFullName[0] != 0)
			strcpy(entry.name, entry.undFullName);

		char *p = strrchr(entry.loadedImageName, '\\');
		if (!p)
			p = entry.loadedImageName;
		else
			++p;

		if (entry.lineFileName[0] == 0)
		{
			if (entry.name[0] == 0)
				sprintf(entry.name, "%p", (void *)entry.offset);

			sprintf(buffer, "%s!%s Line %u\n", p, entry.name, entry.lineNumber);
		}
		else
			sprintf(buffer, "%s!%s Line %u\n", p, entry.name, entry.lineNumber);

		OnOutput(buffer);
	}
}

void as::tools::AsStackWalker::OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr)
{
}

void as::tools::AsStackWalker::OnOutput(LPCSTR szText)
{
	std::string s;
	//if (m_sharedptrlog)
		//s = FormatOutputString("logs", "SharedPtrLog", false);
	//else
	s = FormatOutputString("logs", "CrashLog", false);
	FILE *m_file = fopen(s.c_str(), "a");
	if (!m_file) return;

	printf("   %s", szText);
	fprintf(m_file, "   %s", szText);
	fclose(m_file);
}

#endif

#endif
